﻿using HttpHelper;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using OpenCvSharp;

namespace WebCrawler.Service
{
    public class ImageUtility
    {
        static ImageUtility()
        {
            _http = new HttpHelper.HttpHelper();
        }

        private static HttpHelper.HttpHelper _http;

        public static async Task<Mat> DownloadImageAsync(string url, string fileName, bool save = true, string referer = "https://720yun.com/")
        {
            HttpItem item = new HttpItem()
            {
                URL = url,
                Method = "GET",
                ContentType = "image/jpeg",
                Accept = "image/webp,image/apng,image/*,*/*;q=0.8",
                ResultType = ResultType.Byte,
                Referer = referer
            };

            return await Task.Run(() =>
            {
                HttpResult result = _http.GetHtml(item);

                if (result != null && result.StatusCode == HttpStatusCode.OK)
                {
                    Mat mat = Cv2.ImDecode(result.ResultByte, ImreadModes.Color);

                    if (save && !string.IsNullOrEmpty(fileName))
                    {
                        string folderPath = fileName.Substring(0, fileName.LastIndexOf("/"));
                        if (!Directory.Exists(folderPath))
                        {
                            Directory.CreateDirectory(folderPath);
                        }
                        Cv2.ImWrite(fileName, mat);
                    }
                    return mat;
                }
                else { return null; }
            });
        }

        public static async Task<Mat> MergeSide(string side, string folder, Mat[,] matArray, OpenCvSharp.Size upperLeft, OpenCvSharp.Size lowerRight)
        {
            return await Task.Run(() =>
            {
                int row = matArray.GetLength(1);
                int col = matArray.GetLength(0);
                int width = (col - 1) * upperLeft.Width + lowerRight.Width;
                int height = (row - 1) * upperLeft.Height + lowerRight.Height;

                int singleImageWidth;
                int singleImageHight;

                Mat mat = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC3);
                for (int i = 0; i < row; i++)
                {
                    for (int j = 0; j < col; j++)
                    {
                        if (i < row - 1)
                            singleImageHight = upperLeft.Height;
                        else
                            singleImageHight = lowerRight.Height;

                        if (j < col - 1)
                            singleImageWidth = upperLeft.Width;
                        else
                            singleImageWidth = lowerRight.Width;

                        // 设定兴趣区域ROI
                        Rect roi = new Rect(j * upperLeft.Width, i * upperLeft.Height, singleImageWidth, singleImageHight);

                        // roi指向mat,相当于修改了mat
                        matArray[i, j].CopyTo(new Mat(mat, roi));

                        // 释放
                        matArray[i, j].Dispose();
                    }
                }

                string filePath = $"{folder}/{side}.jpg";
                if (!Directory.Exists(folder))
                {
                    Directory.CreateDirectory(folder);
                }
                Cv2.ImWrite(filePath, mat);

                return mat;
            });
        }

        public static async Task<Mat> MergeCubemapAsync(string folder, Dictionary<string, Mat> mats)
        {
            return await Task.Run(() =>
            {
                Mat up = mats["u"];
                Mat left = mats["l"];
                Mat forward = mats["f"];
                Mat right = mats["r"];
                Mat back = mats["b"];
                Mat down = mats["d"];

                int width = back.Width + left.Width + forward.Width + right.Width;
                int height = down.Height + forward.Height + up.Height;

                int singleImageSideLength = forward.Width;

                Mat cubemap = new Mat(new OpenCvSharp.Size(width, height), MatType.CV_8UC3);

                // 设定兴趣区域ROI
                Rect roi_u = new Rect(1 * singleImageSideLength, 0 * singleImageSideLength, singleImageSideLength, singleImageSideLength);
                Rect roi_l = new Rect(0 * singleImageSideLength, 1 * singleImageSideLength, singleImageSideLength, singleImageSideLength);
                Rect roi_f = new Rect(1 * singleImageSideLength, 1 * singleImageSideLength, singleImageSideLength, singleImageSideLength);
                Rect roi_r = new Rect(2 * singleImageSideLength, 1 * singleImageSideLength, singleImageSideLength, singleImageSideLength);
                Rect roi_b = new Rect(3 * singleImageSideLength, 1 * singleImageSideLength, singleImageSideLength, singleImageSideLength);
                Rect roi_d = new Rect(1 * singleImageSideLength, 2 * singleImageSideLength, singleImageSideLength, singleImageSideLength);

                // roi指向cubemap,相当于修改了cubemap
                up.CopyTo(new Mat(cubemap, roi_u));
                left.CopyTo(new Mat(cubemap, roi_l));
                forward.CopyTo(new Mat(cubemap, roi_f));
                right.CopyTo(new Mat(cubemap, roi_r));
                back.CopyTo(new Mat(cubemap, roi_b));
                down.CopyTo(new Mat(cubemap, roi_d));

                // 释放
                up.Dispose();
                left.Dispose();
                forward.Dispose();
                right.Dispose();
                back.Dispose();
                down.Dispose();

                string filePath = $"{folder}/cubemap.jpg";
                if (!Directory.Exists(folder))
                {
                    Directory.CreateDirectory(folder);
                }
                Cv2.ImWrite(filePath, cubemap);

                return cubemap;
            });
        }

        public static async Task CubemapToPanoramaAsync(string folder, Mat cubemap, int outputWidth = 8192, int outputHeight = 4096)
        {
            await Task.Run(() =>
            {
                Mat panorama = new Mat(new OpenCvSharp.Size(outputWidth, outputHeight), MatType.CV_8UC3);
                //Bitmap bitmap = new Bitmap(outputWidth, outputHeight, PixelFormat.Format32bppRgb);

                double u, v;         // Normalised texture coordinates, from 0 to 1, starting at lower left corner
                double phi, theta;   // Polar coordinates

                int cubeFaceWidth = cubemap.Width / 4;      // 4 horizontal faces
                int cubeFaceHeight = cubemap.Height / 3;    // 3 vertical faces

                for (int i = 0; i < outputHeight; i++)
                {
                    // Rows start from the bottom
                    v = 1 - (double)i / outputHeight;
                    theta = v * Math.PI;

                    for (int j = 0; j < outputWidth; j++)
                    {
                        // Columns start from the left
                        u = (double)j / outputWidth;
                        phi = u * 2 * Math.PI;

                        double x, y, z; // Unit vector
                        x = Math.Sin(phi) * Math.Sin(theta) * -1;
                        y = Math.Cos(theta);
                        z = Math.Cos(phi) * Math.Sin(theta) * -1;

                        double a;
                        a = Math.Max(Math.Abs(x), Math.Abs(y));
                        a = Math.Max(a, Math.Abs(z));

                        // Vector Parallel to the unit vector that lies on one of the cube faces
                        double xa, ya, za;
                        xa = x / a;
                        ya = y / a;
                        za = z / a;

                        int xPixel, yPixel;
                        int xOffset, yOffset;

                        if (xa == 1)
                        {
                            // Right
                            xPixel = (int)((((za + 1f) / 2f) - 1f) * cubeFaceWidth);
                            xOffset = 2 * cubeFaceWidth; // Offset
                            yPixel = (int)((((ya + 1f) / 2f)) * cubeFaceHeight);
                            yOffset = cubeFaceHeight; // Offset
                        }
                        else if (xa == -1)
                        {
                            // Left
                            xPixel = (int)((((za + 1f) / 2f)) * cubeFaceWidth);
                            xOffset = 0;
                            yPixel = (int)((((ya + 1f) / 2f)) * cubeFaceHeight);
                            yOffset = cubeFaceHeight;
                        }
                        else if (ya == 1)
                        {
                            // Up
                            xPixel = (int)((((xa + 1f) / 2f)) * cubeFaceWidth);
                            xOffset = cubeFaceWidth;
                            yPixel = (int)((((za + 1f) / 2f) - 1f) * cubeFaceHeight);
                            yOffset = 2 * cubeFaceHeight;
                        }
                        else if (ya == -1)
                        {
                            // Down
                            xPixel = (int)((((xa + 1f) / 2f)) * cubeFaceWidth);
                            xOffset = cubeFaceWidth;
                            yPixel = (int)((((za + 1f) / 2f)) * cubeFaceHeight);
                            yOffset = 0;
                        }
                        else if (za == 1)
                        {
                            // Front
                            xPixel = (int)((((xa + 1f) / 2f)) * cubeFaceWidth);
                            xOffset = cubeFaceWidth;
                            yPixel = (int)((((ya + 1f) / 2f)) * cubeFaceHeight);
                            yOffset = cubeFaceHeight;
                        }
                        else if (za == -1)
                        {
                            // Back
                            xPixel = (int)((((xa + 1f) / 2f) - 1f) * cubeFaceWidth);
                            xOffset = 3 * cubeFaceWidth;
                            yPixel = (int)((((ya + 1f) / 2f)) * cubeFaceHeight);
                            yOffset = cubeFaceHeight;
                        }
                        else
                        {
                            Console.WriteLine("Unknown face, something went wrong");
                            xPixel = 0;
                            yPixel = 0;
                            xOffset = 0;
                            yOffset = 0;
                        }

                        xPixel = Math.Abs(xPixel);
                        yPixel = Math.Abs(yPixel);

                        xPixel += xOffset;
                        yPixel += yOffset;

                        if (xPixel >= cubemap.Width)
                            xPixel = cubemap.Width - 1;
                        if (yPixel >= cubemap.Height)
                            yPixel = cubemap.Height - 1;

                        panorama.Set(i, j, cubemap.At<Vec3b>(yPixel, xPixel)); // 行 列 是反的
                    }
                }

                string filePath = $"{folder}/panorama.jpg";
                if (!Directory.Exists(folder))
                {
                    Directory.CreateDirectory(folder);
                }
                Cv2.ImWrite(filePath, panorama);

                // 释放
                cubemap.Dispose();
                panorama.Dispose();
            });
        }

        public static async Task<Bitmap> DownloadImageAsync1(string url, string fileName)
        {
            HttpItem item = new HttpItem()
            {
                URL = url,
                Method = "GET",
                ContentType = "image/jpeg",
                Accept = "image/webp,image/apng,image/*,*/*;q=0.8",
                ResultType = ResultType.Byte,
                Referer = "https://720yun.com/"
            };

            return await Task.Run(() =>
            {
                HttpResult result = _http.GetHtml(item);

                if (result != null && result.StatusCode == HttpStatusCode.OK)
                {
                    MemoryStream stream = new MemoryStream(result.ResultByte);
                    Bitmap bitmap = new Bitmap(new MemoryStream(result.ResultByte));
                    stream.Dispose();

                    if (!string.IsNullOrEmpty(fileName))
                    {
                        string folderPath = fileName.Substring(0, fileName.LastIndexOf("/"));
                        if (!Directory.Exists(folderPath))
                        {
                            Directory.CreateDirectory(folderPath);
                        }
                        bitmap.Save(fileName, ImageFormat.Jpeg);
                    }
                    return bitmap;
                }
                else { return null; }
            });
        }

        public static async Task<Bitmap> MergeCubemapAsync1(string folder, Dictionary<string, Bitmap> bitmaps)
        {
            return await Task.Run(() =>
            {
                Bitmap forward = bitmaps["f"];
                Bitmap back = bitmaps["b"];
                Bitmap right = bitmaps["r"];
                Bitmap left = bitmaps["l"];
                Bitmap up = bitmaps["u"];
                Bitmap down = bitmaps["d"];

                int width = back.Width + left.Width + forward.Width + right.Width;
                int height = down.Height + forward.Height + up.Height;

                int singleImageSideLength = forward.Width;

                Bitmap bitmap = new Bitmap(width, height);
                for (int i = 0; i < 3; i++) // 行
                {
                    for (int j = 0; j < 4; j++) // 列
                    {
                        // up
                        if (i == 0 && j == 1)
                        {
                            for (int m = 0; m < singleImageSideLength; m++)
                            {
                                for (int n = 0; n < singleImageSideLength; n++)
                                {
                                    Color color = up.GetPixel(m, n);
                                    bitmap.SetPixel(j * singleImageSideLength + m, i * singleImageSideLength + n, color);
                                }
                            }
                        }

                        // left
                        if (i == 1 && j == 0)
                        {
                            for (int m = 0; m < singleImageSideLength; m++)
                            {
                                for (int n = 0; n < singleImageSideLength; n++)
                                {
                                    Color color = left.GetPixel(m, n);
                                    bitmap.SetPixel(j * singleImageSideLength + m, i * singleImageSideLength + n, color);
                                }
                            }
                        }

                        // forward
                        if (i == 1 && j == 1)
                        {
                            for (int m = 0; m < singleImageSideLength; m++)
                            {
                                for (int n = 0; n < singleImageSideLength; n++)
                                {
                                    Color color = forward.GetPixel(m, n);
                                    bitmap.SetPixel(j * singleImageSideLength + m, i * singleImageSideLength + n, color);
                                }
                            }
                        }

                        // right
                        if (i == 1 && j == 2)
                        {
                            for (int m = 0; m < singleImageSideLength; m++)
                            {
                                for (int n = 0; n < singleImageSideLength; n++)
                                {
                                    Color color = right.GetPixel(m, n);
                                    bitmap.SetPixel(j * singleImageSideLength + m, i * singleImageSideLength + n, color);
                                }
                            }
                        }

                        // back
                        if (i == 1 && j == 3)
                        {
                            for (int m = 0; m < singleImageSideLength; m++)
                            {
                                for (int n = 0; n < singleImageSideLength; n++)
                                {
                                    Color color = back.GetPixel(m, n);
                                    bitmap.SetPixel(j * singleImageSideLength + m, i * singleImageSideLength + n, color);
                                }
                            }
                        }

                        // down
                        if (i == 2 && j == 1)
                        {
                            for (int m = 0; m < singleImageSideLength; m++)
                            {
                                for (int n = 0; n < singleImageSideLength; n++)
                                {
                                    Color color = down.GetPixel(m, n);
                                    bitmap.SetPixel(j * singleImageSideLength + m, i * singleImageSideLength + n, color);
                                }
                            }
                        }
                    }
                }

                // 释放
                forward.Dispose();
                back.Dispose();
                right.Dispose();
                left.Dispose();
                up.Dispose();
                down.Dispose();

                string filePath = $"{folder}/cubemap.jpg";
                if (!Directory.Exists(folder))
                {
                    Directory.CreateDirectory(folder);
                }
                bitmap.Save(filePath, ImageFormat.Jpeg);

                return bitmap;
            });
        }

        public static async Task CubemapToPanoramaAsync1(string folder, Bitmap cubemap, int outputWidth = 8192, int outputHeight = 4096)
        {
            await Task.Run(() =>
            {
                Bitmap bitmap = new Bitmap(outputWidth, outputHeight, PixelFormat.Format32bppRgb);

                double u, v;         // Normalised texture coordinates, from 0 to 1, starting at lower left corner
                double phi, theta;   // Polar coordinates

                int cubeFaceWidth = cubemap.Width / 4;      // 4 horizontal faces
                int cubeFaceHeight = cubemap.Height / 3;    // 3 vertical faces

                for (int i = 0; i < outputHeight; i++)
                {
                    // Rows start from the bottom
                    v = 1 - (double)i / outputHeight;
                    theta = v * Math.PI;

                    for (int j = 0; j < outputWidth; j++)
                    {
                        // Columns start from the left
                        u = (double)j / outputWidth;
                        phi = u * 2 * Math.PI;

                        double x, y, z; // Unit vector
                        x = Math.Sin(phi) * Math.Sin(theta) * -1;
                        y = Math.Cos(theta);
                        z = Math.Cos(phi) * Math.Sin(theta) * -1;

                        double a;
                        a = Math.Max(Math.Abs(x), Math.Abs(y));
                        a = Math.Max(a, Math.Abs(z));

                        // Vector Parallel to the unit vector that lies on one of the cube faces
                        double xa, ya, za;
                        xa = x / a;
                        ya = y / a;
                        za = z / a;

                        int xPixel, yPixel;
                        int xOffset, yOffset;

                        if (xa == 1)
                        {
                            // Right
                            xPixel = (int)((((za + 1f) / 2f) - 1f) * cubeFaceWidth);
                            xOffset = 2 * cubeFaceWidth; // Offset
                            yPixel = (int)((((ya + 1f) / 2f)) * cubeFaceHeight);
                            yOffset = cubeFaceHeight; // Offset
                        }
                        else if (xa == -1)
                        {
                            // Left
                            xPixel = (int)((((za + 1f) / 2f)) * cubeFaceWidth);
                            xOffset = 0;
                            yPixel = (int)((((ya + 1f) / 2f)) * cubeFaceHeight);
                            yOffset = cubeFaceHeight;
                        }
                        else if (ya == 1)
                        {
                            // Up
                            xPixel = (int)((((xa + 1f) / 2f)) * cubeFaceWidth);
                            xOffset = cubeFaceWidth;
                            yPixel = (int)((((za + 1f) / 2f) - 1f) * cubeFaceHeight);
                            yOffset = 2 * cubeFaceHeight;
                        }
                        else if (ya == -1)
                        {
                            // Down
                            xPixel = (int)((((xa + 1f) / 2f)) * cubeFaceWidth);
                            xOffset = cubeFaceWidth;
                            yPixel = (int)((((za + 1f) / 2f)) * cubeFaceHeight);
                            yOffset = 0;
                        }
                        else if (za == 1)
                        {
                            // Front
                            xPixel = (int)((((xa + 1f) / 2f)) * cubeFaceWidth);
                            xOffset = cubeFaceWidth;
                            yPixel = (int)((((ya + 1f) / 2f)) * cubeFaceHeight);
                            yOffset = cubeFaceHeight;
                        }
                        else if (za == -1)
                        {
                            // Back
                            xPixel = (int)((((xa + 1f) / 2f) - 1f) * cubeFaceWidth);
                            xOffset = 3 * cubeFaceWidth;
                            yPixel = (int)((((ya + 1f) / 2f)) * cubeFaceHeight);
                            yOffset = cubeFaceHeight;
                        }
                        else
                        {
                            Console.WriteLine("Unknown face, something went wrong");
                            xPixel = 0;
                            yPixel = 0;
                            xOffset = 0;
                            yOffset = 0;
                        }

                        xPixel = Math.Abs(xPixel);
                        yPixel = Math.Abs(yPixel);

                        xPixel += xOffset;
                        yPixel += yOffset;

                        if (xPixel >= cubemap.Width)
                            xPixel = cubemap.Width - 1;
                        if (yPixel >= cubemap.Height)
                            yPixel = cubemap.Height - 1;

                        Color color = cubemap.GetPixel(xPixel, yPixel);
                        bitmap.SetPixel(j, i, color);
                    }
                }

                string filePath = $"{folder}/panorama.jpg";
                if (!Directory.Exists(folder))
                {
                    Directory.CreateDirectory(folder);
                }
                bitmap.Save(filePath, ImageFormat.Jpeg);

                // 释放
                cubemap.Dispose();
                bitmap.Dispose();
            });
        }
    }
}